package pl.edu.mimuw.mas.agent;

import jade.content.ContentManager;
import jade.content.lang.Codec;
import jade.content.lang.Codec.CodecException;
import jade.content.lang.sl.SLCodec;
import jade.content.onto.Ontology;
import jade.content.onto.OntologyException;
import jade.content.onto.UngroundedException;
import jade.core.Agent;
import jade.domain.DFService;
import jade.lang.acl.ACLMessage;

import java.util.Date;
import java.util.HashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pl.edu.mimuw.mas.agent.behaviour.DFRegisterBehaviour;
import pl.edu.mimuw.mas.agent.behaviour.ServerBehaviour;
import pl.edu.mimuw.mas.agent.gui.AgentMonitorPanel;
import pl.edu.mimuw.mas.agent.gui.IAgentMonitorBE;
import pl.edu.mimuw.mas.chart.Chart;
import pl.edu.mimuw.mas.gui.ITabContener;
import pl.edu.mimuw.mas.ontology.VisualizationCoreOntology;
import pl.edu.mimuw.mas.ontology.concept.Color;
import pl.edu.mimuw.mas.ontology.concept.Point;
import pl.edu.mimuw.mas.ontology.concept.Position;
import pl.edu.mimuw.mas.utils.AgentUtils;
import pl.edu.mimuw.mas.utils.ColorUtils;
import pl.edu.mimuw.mas.visualization.AgentWindowedVisualization;

/**
 * Agent odpowiedzialny za wizualizacje 2D. Na starcie agent oczekuje 4
 * parametrów. Pierwsze dwa dopowiednio wysokość i szerokość mapy; trzeci typu
 * {@link ITabContener} opisujący okno w którym będą wyświetlane infornacje o
 * wizualizacji oraz konfiguracja; czwarty typu logicznego (Boolean)
 * determinujący czy domyślnie wizualizować agentów.
 */
public class Visualization2DAgent extends Agent implements IAgentMonitorBE {
	private static final long serialVersionUID = 450251014654307543L;
	
	public static final String SERVICE_TYPE = "visualization-2d-service";
	public static final String TAB_TITLE = "Agenci 2D";
	
	public static final String VIZ_WINDOW_TITLE = "Wizualizacja 2D";
	public static final int VIZ_WINDOW_X = 512;
	public static final int VIZ_WINDOW_Y = 384;
	
	public static final int GROUND = 0;
	
	public static final int ARGC = 4;

	private final Codec codec = new SLCodec();
	private final Ontology ontology = VisualizationCoreOntology.getInstance();
		
	private final HashMap<String, AgentPossitionExtended> agents = new HashMap<String, AgentPossitionExtended>();	
	
	private Chart chart;
	private boolean displayNewAgents;
	
	private ITabContener mainWindow;
	private AgentMonitorPanel panel;
	private AgentWindowedVisualization agentVisualization; 

	private final Logger log = LoggerFactory.getLogger(getClass());
	
	protected void setup() {
		log.info("{} : aktywny", getName());
		
		if(!setParameters()) {
			return;
		}

		// Rejestruje ontologie do automatycznej serializacji i deserializacji SL
		getContentManager().registerLanguage(codec);
		getContentManager().registerOntology(ontology);	
		log.info(getName() + ": ontologie zarejestrowane");
		
		// Uruchamia gui		
		panel = new AgentMonitorPanel(this);
		mainWindow.addTab(this, TAB_TITLE, panel);
		log.info("{} : monitorowanie uruchomione", getName());

		// Uruchamia wizualizacje
		agentVisualization = new AgentWindowedVisualization(getName() + " - " + VIZ_WINDOW_TITLE, chart, VIZ_WINDOW_X, VIZ_WINDOW_Y);
		agentVisualization.showWindow();
		log.info("{} : wizualizacja uruchomiona", getName());
		
		// Zachowania
		addBehaviour(new DFRegisterBehaviour(this, SERVICE_TYPE, VisualizationCoreOntology.ONTOLOGY_NAME));
		addBehaviour(new ServerBehaviour(this) {
			private static final long serialVersionUID = 7677529063951668524L;

			public boolean doAction(ACLMessage msg, ACLMessage reply) throws UngroundedException, CodecException, OntologyException {				
				// Deserializacja SL
				ContentManager cm = myAgent.getContentManager();
				Position pos = (Position) cm.extractContent(msg);

				if (pos != null) {
					Point pnt = pos.getPoint();
					Color col = pos.getColor();
					boolean agree = processRequest(pos.getAgentId(), 
									pnt.getX(), pnt.getY(), GROUND, 
									col.getR(), col.getG(), col.getB(), 
									pos.getEventDate());
					if (agree) {
						reply.setPerformative(ACLMessage.AGREE);
					} else {
						reply.setPerformative(ACLMessage.REFUSE);
					}
				}
				
				return true;
			}
		});
	}
	
	protected void takeDown() {
		agentVisualization.closeWindow();
		
		log.info("{} : papa", getName());
		try {
			DFService.deregister(this);
		} catch (Exception e) {
			log.debug("Kłopoty z wyrejstrowywaniem", e);
		}
	}
	
	private boolean setParameters() {
		try {
			Object[] obj = getArguments();
			
			chart = new Chart((Integer) obj[0], (Integer) obj[1]);
			mainWindow = (ITabContener) obj[2];
			displayNewAgents = (Boolean) obj[3];
			
			if (obj.length > ARGC) {
				log.warn("{} : część parametrów zostanie zignorowana", getName());
			}

			return true;
			
		} catch (ClassCastException e) {
			log.error("{} : niespodziewany typ", getName());
			doDelete();
		} catch (IndexOutOfBoundsException e) {
			log.error("{} : pusty parametr", getName());
			doDelete();
		} catch (NullPointerException e) {
			log.error("{} : pusty parametr", getName());
			doDelete();
		}
		
		return false;
	}
	
	//
	// IAgentMonitorBE
	//
	public void toggleDisplayStrategy() {
		displayNewAgents = !displayNewAgents;
		log.debug(getName() + " strategia zmieniona");
	}

	public boolean getDisplayStrategy() {
		return displayNewAgents;
	}

	public void deregisterAgentPossition(AgentPossitionExtended ap) {
		agentVisualization.deregisterAgentPossition(ap);
	}

	public void registerAgentPossition(AgentPossitionExtended ap) {
		agentVisualization.registerAgentPossition(ap);
	}
	
	//
	// Obsługa rządań serwera
	//
	
	private boolean isPrintable(String aid) {
		return displayNewAgents;
	}
	
	private boolean processRequest(String aid, int x, int y, float z, int r, int g, int b, Date lastEventDate) {
		AgentPossitionExtended ap = agents.get(aid);

		// Sprawdzanie czy miesci sie na mapie
		if (!chart.isOnChart(x, y)) {
			if (ap != null) {
				deregisterAgentPossition(ap);
			}
			log.debug("{} wypada poza mapę", aid);
			return false;
		}
		
		// Sprawdzanie czy poprawny kolor
		if (!ColorUtils.isValid(r, g, b)) {
			if (ap != null) {
				deregisterAgentPossition(ap);
			}
			log.debug("{} chciał się wizualizować w niepoprawnym kolorze", aid);
			return false;
		}
		
		// Poprawne dane - odświerz dane
		if (ap != null) {
			ap.setLastEventDate(lastEventDate);
			ap.setPossition(x, y, z);

			if (!ap.isOverrideColor()) {
				ap.setColor(r, g, b);	
			}

			panel.refreshAgentPossition(aid);
			return ap.isPrintable();
		}

		// Dodaj nowego agenta
		boolean printable = isPrintable(aid);
		ap = new AgentPossitionExtended(aid, AgentUtils.extractLocalName(aid), lastEventDate, x, y, z, printable, r, g, b);
		
		agents.put(aid, ap);		
		panel.putAgentPossition(ap);
		
		if (printable) {
			registerAgentPossition(ap);
		}

		return printable;
	}
}
